import requests,json

import calendar

from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

 

def ja_db(dc ,act='get',host=None):
    """ http读写数据库 
    """
    if host is None:
        host='oq-upinfo.17121.top:2028'
    tpl = "http://{}/jsonapi/{}"
    url=tpl.format(host,act)
    data = json.dumps(dc,   default=datetime.isoformat)
    #--获取账号信息
    res_obj = requests.post(url ,data=data,timeout=25)#不能发送中文
    if res_obj.status_code != 200:
        logger.warning('访问 %s 服务错误 【%s】  / %s'%(url, res_obj.status_code, res_obj.text))
    #print(res_obj.text )
    dat_dc = json.loads(res_obj.text)
    return dat_dc 


class 补考勤报告():
    """  通过类完成
* 勤假记录读取，
* 对照出勤表完成  缺勤对照
* 计算出补卡记录


公出需要计算成  班次加入到考勤记录

    """
    one = -1  # 首次运行
    res = {}  # 资源
    
    def _mkQuerDate(self, year: int, mon: int) -> list[str]:
        """创建 本月第一天 + 下个月第一天
        
        Args:
            year (int): 年份
            mon (int): 月份
        
        Returns:
            list[str]: ["YYYY-MM-01", "YYYY-MM-01"]（本月第一天和下个月第一天）
        
        Example:
            >>> _mkQuerDate(2023, 12)
            ['2023-12-01', '2024-01-01']  # 自动跨年
        """
        # 本月第一天
        first_day = f"{year}-{mon:02d}-01"
        
        # 计算下个月的第一天
        if mon == 12:
            next_month = 1
            next_year = year + 1
        else:
            next_month = mon + 1
            next_year = year
        
        next_first_day = f"{next_year}-{next_month:02d}-01"
        
        return [first_day, next_first_day]
        
    def _mkQueryData(self,name:str,dayFirst:str,dayLast:str):
        """ 返回查询 结构的json  占位替换
            args:
                name:str
                dayFirst:str
                dayLast:str
                
            returns:
        """
        jdata={
    "oa/T_USER@用户": {
        "@column": "ID,NAME,DEFAULTDEPARTMENT.部门id,TELEPHONE.手机,LOGINNO.登入ID",
        "NAME": name,
        "@limit": 1
    },   
    "oa/TLK_ATTENDANCEDETAIL@补考勤": {
        "AUTHOR|": "oa/T_USER@用户.ID",
        "ITEM_ATTDATE&": f">={dayFirst},<={dayLast}",
        "@order": "-CREATED",
        "@column": "ITEM_ATTDATE, FORMNAME,AUTHOR,CREATED"
    },
    "oa/TLK_APPLY_LEAVE@请假": {
        "AUTHOR|": "oa/T_USER@用户.ID",
        "ITEM_LEAVE_T&": f">={dayFirst},<={dayLast}",
        "@order": "-CREATED",
        "@column": "AUTHOR, ITEM_START_T.开始时间,ITEM_LEAVE_T.结束时间,ITEM_LEAVE_R.理由,ITEM_APPLICANT,CREATED"
    },
    "oa/TLK_BUSINESS_PUBLIC_TRIP@公出": {
        "AUTHOR|": "oa/T_USER@用户.ID",
        "ITEM_TRAVEL_OT&":  f">={dayFirst},<={dayLast}",
        "@order": "-CREATED",
        "@column": "AUTHOR,ITEM_TRAVEL_ST,ITEM_TRAVEL_OT,ITEM_TRAVEL_R.理由, ITEM_TRAVEL_CITY.公出城市, FORMNAME.模块,STATELABELINFO,CREATED",
        "#url": "http://127.0.0.1:2028/jsonapi/get"
    },
        "kq/USERINFO@考情信息": {
        "@column": "BADGENUMBER.用户id,NAME,USERID.考勤id,DEFAULTDEPTID",
        "NAME": name,
        "@limit": 1
    },
    "kq/CHECKINOUT@考情记录": {
        "@column": "USERID.考勤id,CHECKTIME",
        "USERID|": "kq/USERINFO@考情信息.考勤id",
        "CHECKTIME&": f">={dayFirst},<={dayLast}",
        "@order": "-CHECKTIME"
    }
}
        
        return jdata
        
    def _后勤时间判断v1(self,str4time:str)->str:
        """ 日期判断 打卡间段  后勤
            含10:30之前都算上班
            含17:00之后都算下班
            11:30到13:30:00 算中午打卡
        args:
            str4time: "2025-11-11 08:00:00"
        returns:
            str:
                - "上班"
                - "下班"
                - "中午"
                - "未知"
                """
        te = str4time[11:]
        if te<="10:30:00":
            return "早上"
        if te>="17:00:00":
            return "晚上"
        if te<"13:30:00" and te>"10:30:00" :
            return "中午"
        return "未知"

    def _后勤标记一行(self,r):
        """  对日期和类型打标记  
        args:
            r dict:   
        """
        #print(r['CHECKTIME'])
        d = datetime.strptime(r['CHECKTIME'], "%Y-%m-%d %H:%M:%S")
        r['时间日期'] = d 
        r['星期'] = d.weekday()+1
        #r['月份'] = d.month 
        r['日期'] = r['CHECKTIME'][:10]
        r['时间'] = r['CHECKTIME'][11:]
        r['早中晚'] = self._后勤时间判断v1(r['CHECKTIME'])
        r['班次'] = '空'
        
        return r 
   
    def _后勤加班日期(self,r):
        """ 判断 是否加班 """
        if r['星期'] == 7:
            r['班次'] = "加班"+r['班次'] 
        return r
    
    def _更新班次(self,df):
        """ 判断 【上班，下班，中午上，中午下】更新班次 """
        
        # 中午上
        noon_records = df[df['早中晚'] == '中午']
        time_indices = noon_records.groupby('日期')['时间日期'].idxmax()
        df.loc[time_indices, '班次'] = "中午上"
        # 中午下
        noon_records = df[df['早中晚'] == '中午']
        time_indices = noon_records.groupby('日期')['时间日期'].idxmin()
        df.loc[time_indices, '班次'] = "中午下"
        
        # 上班
        noon_records = df[df['早中晚'] == '早上']
        time_indices = noon_records.groupby('日期')['时间日期'].idxmin()
        df.loc[time_indices, '班次'] = "上班"
        
        # 下班
        noon_records = df[df['早中晚'] == '晚上']
        time_indices = noon_records.groupby('日期')['时间日期'].idxmax()
        df.loc[time_indices, '班次'] = "下班"
        
    def 标准日历(self,year:int,mon:int ):
        """  每一天 4 条数据
        args:
            year:int
            mon:int
           
        returns:
            list : [{'日期': '2024-03-03', '班次': '早'},]
        """
        
        sdate = datetime (year,mon,1)
        edate =  sdate +  relativedelta(months=1)  + timedelta(days=-1)
        r = []
        tdate = sdate
        # 多少天
        for i in range (0,calendar.monthrange( sdate.year,sdate.month )[1]):
            #
            weekday = tdate.weekday() + 1
            vdate = tdate.strftime("%Y-%m-%d")
        
            r.append({"标准":'上班',"打卡点":vdate+" 08:00","日期":vdate,"阶段":"上班","星期":str(weekday)})
            r.append({"标准":'上班',"打卡点":vdate+" 11:30","日期":vdate,"阶段":"中午上","星期":str(weekday)})
            r.append({"标准":'上班',"打卡点":vdate+" 12:30","日期":vdate,"阶段":"中午下","星期":str(weekday)})
            r.append({"标准":'上班',"打卡点":vdate+" 17:00","日期":vdate,"阶段":"下班","星期":str(weekday)})
            tdate = tdate + timedelta(days=1)            
        return r
    
    
    def _出勤表v1(self,year:int,mon:int ):
        """  应出勤日历
        args:
            year:int
            mon:int
           
        returns:
            list : [{'日期': '2024-03-03', '班次': '早'},]
        """
        
        sdate = datetime (year,mon,1)
        edate =  sdate +  relativedelta(months=1)  + timedelta(days=-1)
        r = []
        tdate = sdate
        # 多少天
        for i in range (0,calendar.monthrange( sdate.year,sdate.month )[1]):
            #
            weekday = tdate.weekday() + 1
            vdate = tdate.strftime("%Y-%m-%d")
            # 跳过周末
            if  weekday in [7] and True :
                
                r.append({"补卡点":vdate+" 08:00","日期":vdate,"阶段":"加班上班","星期":str(weekday)})
                r.append({"补卡点":vdate+" 11:30","日期":vdate,"阶段":"加班中午上","星期":str(weekday)})
                r.append({"补卡点":vdate+" 12:30","日期":vdate,"阶段":"加班中午下","星期":str(weekday)})
                r.append({"补卡点":vdate+" 17:00","日期":vdate,"阶段":"加班下班","星期":str(weekday)})
                tdate = tdate + timedelta(days=1)
                continue
            r.append({"补卡点":vdate+" 08:00","日期":vdate,"阶段":"上班","星期":str(weekday)})
            r.append({"补卡点":vdate+" 11:30","日期":vdate,"阶段":"中午上","星期":str(weekday)})
            r.append({"补卡点":vdate+" 12:30","日期":vdate,"阶段":"中午下","星期":str(weekday)})
            r.append({"补卡点":vdate+" 17:00","日期":vdate,"阶段":"下班","星期":str(weekday)})
            tdate = tdate + timedelta(days=1)            
        return r
    
    def getKqData(self,name:str,mon:int,year:int):
        """  """
        """   打卡考勤 获取"""
        # 开始和结束日期
        sdDate = self._mkQuerDate(year,mon)
        vn = self._mkQueryData(name,sdDate[0], sdDate[1])
        # 查询
        vd = ja_db(  vn )
        
        return vd
        
    def getJqDataV2(self,name:str,mon:int,year:int):
        """  假勤 4项记录 内部
            做日期和类型groupby 重复
        """
        # 获取考勤，开始和结束日期
        vd = self.getKqData(name,mon,year)
        # 考勤主数据
        df=pd.DataFrame(vd["kq/CHECKINOUT@考情记录"])
        df["类型"] = '打卡'
        #----公出
        rows = []
        rows = rows + self.公出记录转假勤(vd)
        rows = rows +self.请假记录转假勤(vd)
        rows = rows +self.补考勤记录转假勤(vd)
        addDf=pd.DataFrame(rows)
        
        dfAll = pd.concat([df, addDf], ignore_index=True)
        #--标记早中晚
        chekdf = dfAll.apply( self._后勤标记一行 ,axis=1)
        # --标记上下班
        self._更新班次(chekdf)
        # 无效勤假
        chekdf = chekdf[chekdf["班次"] != "空"]
        # -- 标记周日加班
        chekdf = chekdf.apply( self._后勤加班日期 ,axis=1)
        return chekdf
    
    def getJqTab(self,name:str,mon:int,year:int,ignore:list=[]):
        """ 获取假勤 记录表
        args:
            名字
            月份
            年份
            假日
        """
        chekdf = self.getJqDataV2(name,mon,year)
        # 创建日历
        r = self._出勤表v1(year,mon)
        rdf =  pd.DataFrame(r)
        # 合并两表
        result = pd.merge(
            rdf,
            chekdf,
            left_on=["日期", "阶段"],  # 左表关联字段
            right_on=["日期", "班次"],  # 右表关联字段
            how="left"  # LEFT JOIN
        )
        showCol = ["阶段","日期","星期_x","类型","时间","班次","补卡点"]
        retDf =result[showCol]
        #-- 去掉空加班日历
        
        retDf = retDf[ ~ (( retDf["时间"]  .isna() == True  )&( retDf["星期_x"] == "7" ) )]
        #-- 节假日
        retDf = retDf[ ~ (( retDf["时间"]  .isna() == True  )&( retDf["日期"].isin(ignore) ) )]
        return retDf 
    
    def 公出记录转假勤(self,data):
        """ 通过数据转换 成假勤记录 """
        rows = data['oa/TLK_BUSINESS_PUBLIC_TRIP@公出']
        reList = []
        for r in rows:
            
            _ = self._记录转假勤(r['ITEM_TRAVEL_ST'],r['ITEM_TRAVEL_OT'],'公出')
            reList = reList + _
        #---------
        #print(reList)
        return  reList
    
    def 请假记录转假勤(self,data):
        """ 通过数据转换 成假勤记录 """
        rows = data['oa/TLK_APPLY_LEAVE@请假']
        reList = []
        for r in rows:
            
            _ = self._记录转假勤(r['开始时间'],r['结束时间'],'请假')
            reList = reList + _
        #---------
        #print(reList)
        return  reList
        
    def 补考勤记录转假勤(self,data):
        """   """
        rows = data['oa/TLK_ATTENDANCEDETAIL@补考勤']
        reList = []
        for r in rows:
            reList .append( {'类型': '补考勤', 'CHECKTIME': r['ITEM_ATTDATE']})
        #---------
        #print(reList)
        return  reList
            
    def _记录转假勤(self,stime:str,etime:str,te:str):
        """  时间段内经历多少个 打卡时间
        args:
            stime:str 开始时间
            etime:str 结束时间
            te:str 类型
            
        returns：
            list：[ {'类型': '请假', 'CHECKTIME': '2025-03-05 11:43:37'},]
            """
        

        start_time = datetime.strptime(stime, "%Y-%m-%d %H:%M:%S")
        end_time = datetime.strptime(etime, "%Y-%m-%d %H:%M:%S")
        total_minutes = int((end_time - start_time).total_seconds() / 60)  # 计算总分钟数
        # 标准时间表
        target_times = [
            datetime.strptime("08:00", "%H:%M").time(),
            datetime.strptime("17:00", "%H:%M").time(),
            datetime.strptime("11:30", "%H:%M").time(),
            datetime.strptime("12:30", "%H:%M").time()
        ]
        reList = []
        # 使用 for 循环 + range 遍历每分钟
        for minute in range(total_minutes + 1):  # +1 确保包含结束时间
            current_time = start_time + timedelta(minutes=minute)
            current_time_part = current_time.time()
                      
            if current_time_part in target_times:
                _ = {'类型': te, 'CHECKTIME': current_time.strftime("%Y-%m-%d %H:%M:%S")}
                reList.append(_)
        #--------
        return reList
    
    def 补卡信息生成(self, year,mon ,name ,ignore):
        """  可用于发送的 补卡提示 """
        #import 补考勤报告   
        #补考勤报告 = reload(补考勤报告)
        #bk = 补考勤报告.补考勤报告()
        df = self.getJqTab(name,mon,year,ignore)
        sendDf = df[ (df["时间"].isna() == True )   ]
        retStr = '通过查询{}年{}月发现 以下日期需要补卡'.format(year,mon)
        if len(sendDf) == 0:
            return ""
        for row in sendDf.itertuples():
            tmp = "{}周{} {}未打卡 建议补卡 {}"
            tmp = tmp.format(row.日期,row.星期_x ,row.阶段 ,row.补卡点)
            retStr = retStr +"\n"+ tmp 
        retStr = retStr+"\n" + "以上信息用于参考 快速进入<a  href=\"http://oa.ouqiyj.com:8908/ouqi/\" >OA系统</a>"
        return  retStr
    


    def is_time_in_range(target_time, start_time, end_time):
        """
        值  是否在指定时间范围内  #其他数据证明是在上班
        
        :param target_time: 目标时间（字符串或datetime对象）
        :param start_time: 范围开始时间（字符串或datetime对象）
        :param end_time: 范围结束时间（字符串或datetime对象）
        :return: bool
        """
        # 统一转换为datetime对象
        if isinstance(target_time, str):
            target_time = datetime.strptime(target_time, "%Y-%m-%d %H:%M:%S")
        if isinstance(start_time, str):
            start_time = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
        if isinstance(end_time, str):
            end_time = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
        
        return start_time <= target_time <= end_time



def main(name,year,mon):
    
    self = 补考勤报告()
    df = self.getJqTab(name,mon,year)
    df.to_excel('d:\kq{}excel{}-{}.xlsx'.format(name,year,mon) , sheet_name='Sheet1', index=False)
    return df

def do(nameList,year,mon):
    for name in nameList:
        self = 补考勤报告()
        df = self.getJqTab(name,mon,year)
        df.to_excel('d:\kq\{}excel{}-{}.xlsx'.format(name,year,mon) , sheet_name='Sheet1', index=False)  




if __name__ == "__main__":
    
    name='蔡金凯'
    nameList=['蔡金凯','裘寅','张林','叶丹-财务','刘云云',"柴丹飘","童杰",'厉青','陈肖璇','戴可科']
    mon=4
    year=2025
    #print(main(name,year,mon))
    do(nameList,year,mon)
